package cn.wang.service;

import cn.wang.client.BrandClient;
import cn.wang.client.CategoryClient;
import cn.wang.client.GoodsClient;
import cn.wang.client.SpecificationClient;
import cn.wang.pojo.*;
import cn.wang.repository.GoodsRepository;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.*;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class GoodsService {


    @Autowired
    GoodsClient goodsClient;
    @Autowired
    BrandClient brandClient;
    @Autowired
    CategoryClient categoryClient;
    @Autowired
    SpecificationClient speClient;
    @Autowired
    GoodsRepository repository;
    @Autowired
    ElasticsearchTemplate template;


    /**
     * 根据传入的spu,转成goods,
     *
     * @return
     */
    public Goods spu2Goods(Spu spu) {
        Goods goods = new Goods();
        // 查询spu下的所有sku信息
        Long spuId = spu.getId();
        String all = "";
        List<Sku> skuList = goodsClient.querySkusBySpuId(spuId);    // 查询spu下的所有的sku
        List<Map<String, Object>> skus = new ArrayList<>(); // 对sku中的数据进行精简
        List<Long> priceList = new ArrayList<>();
        // 保留sku中的 price, image, id title 字段, 其余的不要
        for (Sku sku : skuList) {
            HashMap<String, Object> skuMap = new HashMap<>();
            skuMap.put("price", sku.getPrice());
            skuMap.put("image", StringUtils.substringBefore(sku.getImages(), ",")); // 只要sku中的第一个图片地址
            skuMap.put("id", sku.getId());
            skuMap.put("title", sku.getTitle());
            priceList.add(sku.getPrice());
            skus.add(skuMap);
        }

        Brand brand = brandClient.queryBrandByBId(spu.getBrandId());
        List<Category> categories = categoryClient.queryCategorysByCids(spu.getCid1(), spu.getCid2(), spu.getCid3());
        List<String> categoryName = categories.stream().map(Category::getName).collect(Collectors.toList());
        // all 中存放 品牌 spu的标题, 分类等信息
        all = spu.getTitle() + " " + brand.getName() + " " + StringUtils.join(categoryName, " ");

        // 查询商品参数及其键值对
        // 1. 查询对应分类下的所有规格参数对象(只获取用于搜索的)
        List<SpecParam> params = speClient.getParams(null, spu.getCid3(), true);// 根据商品分类查询上架商品
        // 获取spuDetail对象, 从其中获取spu的规格参数的值
        SpuDetail spuDetail = goodsClient.querySpuBySpuId(spuId);
        // 获取通用规格参数
        Map<Long, String> GenericMap = JSON.parseObject(spuDetail.getGenericSpec(), new TypeReference<Map<Long, String>>() {
        });
        // 获取特有规格参数
        Map<Long, List<String>> SpecialMap = JSON.parseObject(spuDetail.getSpecialSpec(), new TypeReference<Map<Long, List<String>>>() {
        });
        // 转成键名对应值的格式
        HashMap<String, Object> paramMap = new HashMap<>();
        for (SpecParam param : params) {
            Object value = null;
            if (param.getGeneric()) {   // 如果为param为通用的就在通过的map中获取
                value = GenericMap.get(param.getId());  // 根据param的id获取对应的值
                if (param.getNumeric()) {           // 如果是数字类型,直转成对应区间的字符串, 便于过滤时聚合
                    value = chooseSegment(value.toString(), param);
                }
            } else {
                value = SpecialMap.get(param.getId());
            }
            paramMap.put(param.getName(), value);
        }
        goods.setSubTitle(spu.getSubTitle());
        goods.setBrandId(spu.getBrandId());
        goods.setCid1(spu.getCid1());
        goods.setCid2(spu.getCid2());
        goods.setCid3(spu.getCid3());
        goods.setCreateTime(spu.getCreateTime());
        goods.setId(spuId);
        goods.setSkus(JSON.toJSONString(skus)); //  存法sku列表的json的数据格式
        goods.setPrice(priceList); // 存放sku的价格
        goods.setSpecs(paramMap); //  存放规格参数的键值对
        goods.setAll(all); // 存放可以被搜索的字段如: 标题, 商品分类, 品牌
        return goods;
    }


    private String chooseSegment(String value, SpecParam p) {
        double val = NumberUtils.toDouble(value);
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = NumberUtils.toDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = NumberUtils.toDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    /**
     * 根据所有键和过滤字段对结果进行过滤
     *
     * @param request
     * @return
     */
    public SearchResult searchPage(SearchRequest request) {
        int page = request.getPage() - 1; // 设置page, elasticsearch的页码时从0开始, 页面中的页码从1开始, -1进行转换
        int size = request.getSize();
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); // 搜索条件构造器
        // 过滤字段, 设置最后展示的字段
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "subTitle", "skus"}, null));
        // 分页
        queryBuilder.withPageable(PageRequest.of(page, size));
        // match搜索
        QueryBuilder basicsSearch = builderQeury(request);
        queryBuilder.withQuery(basicsSearch);

        String categoryAggName = "categoryAgg";
        String brandAggName = "brandAgg";
        // 对商品分类聚合
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("cid3"));
        // 对商品品牌聚合
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));

        AggregatedPage<Goods> goodsPage = template.queryForPage(queryBuilder.build(), Goods.class);
        // 直接来总页数返回的是1, 存在问题, 这里根据总条数和每页多少条进行计算
        long totalElements = goodsPage.getTotalElements();
        List<Goods> content = goodsPage.getContent();
        int totalPages = (int) (totalElements / size == 0 ? totalElements / size : (totalElements / size + 1));
        // 根据查询完的结果在进行聚合
        Aggregations aggregations = goodsPage.getAggregations();
        List<Category> categoryList = ParsedCategoryAgg(aggregations.get(categoryAggName)); // 解析聚合的结果
        List<Brand> brandList = ParsedBrandAgg(aggregations.get(brandAggName)); // 解析聚合的结果
        // 聚合规格参数
        List<Map<String, Object>> specList = null;
        if (categoryList.size() == 1) { // 这里可以改造为根据商品类别多的进行聚合, 但是这里引索中只有一个手机品类的数据改造了可看不到结果
            specList = aggSpec(categoryList.get(0).getId(), basicsSearch);
        }
        return new SearchResult(totalElements, totalPages, content, categoryList, brandList, specList);
    }

    /**
     * 构建搜索条件
     *
     * @param request
     * @return
     */
    private QueryBuilder builderQeury(SearchRequest request) {
        // 使用bool搜索
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 使用must(多个条件都要满足)
        // 设置搜索条件
        boolQueryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()));
        // 设置过滤条件
        Map<String, Object> specMap = request.getFilter();
        if (!CollectionUtils.isEmpty(specMap)) {
            for (Map.Entry<String, Object> entry : specMap.entrySet()) {
                String key = entry.getKey();
                if (!"brandId".equals(key) && !"cid3".equals(key)) { // 如果条件不是brandId和cid3,就是规格参数, 这些字段需要加上specs和keywored
                    key = "specs." + key + ".keyword";
                }
                Object value = entry.getValue();
                boolQueryBuilder.filter(QueryBuilders.termsQuery(key, value));
            }
        }
        return boolQueryBuilder;
    }

    /**
     * 在搜索结果的条件的基础上进行聚合
     * @param id
     * @param basicsSearch
     * @return
     */
    private List<Map<String, Object>> aggSpec(Long id, QueryBuilder basicsSearch) {
        List<Map<String, Object>> aggSpec = new ArrayList<>();
        // 查询商品分类下的所有规格参数
        List<SpecParam> params = speClient.getParams(null, id, true);
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        queryBuilder.withQuery(basicsSearch);
        // 对每一个规格参数进行聚合
        for (SpecParam param : params) {
            String name = param.getName();
            queryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs." + name + ".keyword"));
        }
        AggregatedPage<Goods> goodsPage = template.queryForPage(queryBuilder.build(), Goods.class);
        Aggregations aggregations = goodsPage.getAggregations();
        // 解析聚合结果
        for (SpecParam param : params) {
            StringTerms terms = aggregations.get(param.getName());
            // 获取聚合结果中的值, 简介过转成字符串(规格参数的值)集合
            List<String> SpecValueList = terms.getBuckets().stream().map(b -> b.getKeyAsString()).collect(Collectors.toList());
            HashMap<String, Object> map = new HashMap<>();
            map.put("k", param.getName());
            map.put("options", SpecValueList);
            aggSpec.add(map);
        }
        return aggSpec;
    }

    private List<Brand> ParsedBrandAgg(LongTerms terms) {
        // 获取所有同桶中品牌id
        List<Long> ids = terms.getBuckets().stream().
                map(bucket -> bucket.getKeyAsNumber().longValue())
                .collect(Collectors.toList());
        List<Brand> brands = brandClient.queryBrandsByBIds(ids.toArray(new Long[]{}));
        return brands;
    }

    private List<Category> ParsedCategoryAgg(LongTerms terms) {
        // 获取所有同桶中品牌id
        List<Long> ids = terms.getBuckets().stream().
                map(bucket -> bucket.getKeyAsNumber().longValue())
                .collect(Collectors.toList());
        List<Category> categorys = categoryClient.queryCategorysByCids(ids.toArray(new Long[]{}));
        return categorys;
    }

    /**
     * 根据spuId创建或更新引索
     * @param spuId
     */
    public void indexUpdateOrCreate(Long spuId) {
        Spu spu = goodsClient.queryGoodBySpuId(spuId);
        Goods goods = spu2Goods(spu);
        repository.save(goods);
    }

    public void indexDelete(Long spuId) {
        repository.deleteById(spuId);
    }
}
